package cronjob

import (
	"context"
	"encoding/json"
	"fmt"
	"peilian-api/app/controllers/web_report"
	"peilian-api/app/global/databases/tables"
	"peilian-api/app/global/variable"
	"peilian-api/app/model"
	"peilian-api/utils/tools"
	"strconv"
	"strings"

	"github.com/elastic/go-elasticsearch/v7"
	"github.com/elastic/go-elasticsearch/v7/esutil"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

type Document struct {
	UserID        uint    `json:"user_id"`
	Education     string  `json:"education"`
	SessionID     uint    `json:"session_id"`
	School        string  `json:"school"`
	Academy       string  `json:"academy"`
	Profession    string  `json:"profession"`
	Grade         string  `json:"grade"`
	TrainAt       int64   `json:"train_at"`
	Score         float64 `json:"score"`
	CareerName    string  `json:"career_name"`
	AgentId       uint    `json:"agent_id"`
	UserType      string  `json:"user_type"`
	UserName      string  `json:"user_name"`
	CareerId      uint    `json:"career_id"`
	ExpressScore  float64 `json:"express_score"`
	EmphasisScore float64 `json:"emphasis_score"`
	LogicScore    float64 `json:"logic_score"`
}

type LabelDoc struct {
	SessionId uint   `json:"session_id"`
	UserId    uint   `json:"user_id"`
	Type      string `json:"type"` // pro:pos/pro:neg/gen:pos/gen:neg/experience/tool/personality/career_recommend
	Score     int    `json:"score"`
	Label     string `json:"label"`
}

type Score struct {
	tables.ScoreFeedBack
	Score float64
}

func SessionDataSyncWithElasticsearch(sessionId int) {
	const syncRedisKey = "data_view_sync_with_elasticsearch_session_id"
	const lockRedisKey = "data_view_sync_with_elasticsearch_session_id_lock"
	defer tools.RecoverGoroutine()
	defer variable.DB.Redis.Set(lockRedisKey, "unlocked", 0)
	lockKey, err := variable.DB.Redis.Get(lockRedisKey).Result()
	if err != nil {
		variable.DB.Redis.Set(lockRedisKey, "locked", 0)
		lockKey = "unlocked"
	}
	if lockKey == "locked" {
		return
	}
	var isWrite = true // 是否写入redis
	var whereStr = "finish_at <> '0000-00-00 00:00:00.000' and url <> ''"
	if sessionId == 0 {
		sid, err := variable.DB.Redis.Get(syncRedisKey).Result()
		if err != nil {
			variable.DB.Redis.Set(syncRedisKey, "0", 0)
			return
		}
		sessionId, _ = strconv.Atoi(sid)
		whereStr = fmt.Sprintf("id > %d and %s", sessionId, whereStr)
	} else {
		whereStr = fmt.Sprintf("id = %d and %s", sessionId, whereStr)
		isWrite = false // 不写入redis
	}
	sessions := make([]*tables.Session, 0)
	if err := variable.DB.MySQL.
		Where(whereStr).
		Select(
			"UserID", "ID", "School", "Academy", "Profession", "Grade", "TrainAt", "Evaluation",
			"TotalScore", "CareerName", "Type", "UserName", "Education", "CareerID",
		).Order("id asc").Session(&gorm.Session{Logger: logger.Default.LogMode(logger.Error)}).
		Preload("User").Find(&sessions).Error; err != nil {
		return
	}
	cfg := elasticsearch.Config{
		Addresses: []string{
			fmt.Sprintf("http://%s:%d", variable.Config.Elasticsearch.Host, variable.Config.Elasticsearch.Port),
		},
		Username: variable.Config.Elasticsearch.UserName,
		Password: variable.Config.Elasticsearch.Password,
	}
	es, err := elasticsearch.NewClient(cfg)
	if err != nil {
		return
	}
	bulkItems := make([]*esutil.BulkIndexerItem, 0)
	labelItems := make([]*esutil.BulkIndexerItem, 0)
	var lastSessionId uint
	for _, s := range sessions {
		var agentId uint
		if s.User != nil {
			agentId = s.User.AgentID
		}
		scores := []Score{}
		variable.DB.MySQL.Model(&tables.ScoreFeedBack{}).Where("session_id=?", s.ID).Scan(&scores)
		doc := Document{
			UserID:     s.UserID,
			SessionID:  s.ID,
			School:     s.School,
			Academy:    s.Academy,
			Profession: s.Profession,
			Grade:      s.Grade,
			TrainAt:    s.TrainAt.Unix(),
			Score:      s.TotalScore,
			CareerName: s.CareerName,
			AgentId:    agentId,
			UserType:   s.Type,
			UserName:   s.UserName,
			CareerId:   s.CareerID,
			Education:  s.Education,
		}
		for _, s := range scores {
			switch s.ScoreType {
			case "expressive":
				doc.ExpressScore = s.Score
			case "theme":
				doc.EmphasisScore = s.Score
			case "logic":
				doc.LogicScore = s.Score
			}
		}
		docByte, _ := json.Marshal(doc)
		bulkItems = append(bulkItems, &esutil.BulkIndexerItem{
			Index:      variable.Config.Elasticsearch.Index,
			Action:     "index",
			DocumentID: strconv.Itoa(int(s.ID)),
			Body:       strings.NewReader(string(docByte)),
		})
		if s.Evaluation != "" {
			var evaluateResp model.LabelEvaluate
			if err := json.Unmarshal([]byte(s.Evaluation), &evaluateResp); err != nil {
				goto A
			}
			labels := make([]*LabelDoc, 0)
			report := web_report.DefaultWebReport{ReportData: &web_report.ReportData{}}
			report.SetEvaluateResp(evaluateResp)
			for _, l := range report.GetPosProfessionalData() {
				labels = append(labels, &LabelDoc{
					SessionId: s.ID,
					UserId:    s.UserID,
					Type:      "pro:pos",
					Label:     l.Label,
					Score:     l.Value,
				})
			}
			for _, l := range report.GetNegProfessionalData() {
				labels = append(labels, &LabelDoc{
					SessionId: s.ID,
					UserId:    s.UserID,
					Type:      "pro:neg",
					Label:     l.Label,
					Score:     l.Value,
				})
			}
			for _, l := range report.GetPosGeneralData() {
				labels = append(labels, &LabelDoc{
					SessionId: s.ID,
					UserId:    s.UserID,
					Type:      "gen:pos",
					Label:     l.Label,
					Score:     l.Value,
				})
			}
			for _, l := range report.GetNegGeneralData() {
				labels = append(labels, &LabelDoc{
					SessionId: s.ID,
					UserId:    s.UserID,
					Type:      "gen:neg",
					Label:     l.Label,
					Score:     l.Value,
				})
			}
			for _, l := range report.GetExperienceData().Advantage {
				labels = append(labels, &LabelDoc{
					SessionId: s.ID,
					UserId:    s.UserID,
					Type:      "experience:pos",
					Label:     l.Label,
					Score:     l.Value,
				})
			}
			for _, l := range report.GetExperienceData().Promoted {
				labels = append(labels, &LabelDoc{
					SessionId: s.ID,
					UserId:    s.UserID,
					Type:      "experience:neg",
					Label:     l.Label,
					Score:     l.Value,
				})
			}
			for _, l := range report.GetInstrumentalData() {
				labels = append(labels, &LabelDoc{
					SessionId: s.ID,
					UserId:    s.UserID,
					Type:      "tool",
					Label:     l,
				})
			}
			for _, l := range report.GetPersonalData() {
				labels = append(labels, &LabelDoc{
					SessionId: s.ID,
					UserId:    s.UserID,
					Type:      "personality",
					Label:     l.Label,
					Score:     l.Value,
				})
			}
			if s.User != nil && s.User.RecommendCareer != "" {
				recommendCareerNameLi := strings.Split(s.User.RecommendCareer, ",")
				for _, r := range recommendCareerNameLi {
					labels = append(labels, &LabelDoc{
						SessionId: s.ID,
						UserId:    s.UserID,
						Type:      "career_recommend",
						Label:     r,
					})
				}
			}
			for i, l := range labels {
				labelDocByte, _ := json.Marshal(l)
				labelItems = append(labelItems, &esutil.BulkIndexerItem{
					Index:      variable.Config.Elasticsearch.LabelIndex,
					Action:     "index",
					DocumentID: fmt.Sprintf("%d:%d:%d", s.ID, s.UserID, i),
					Body:       strings.NewReader(string(labelDocByte)),
				})

			}
		}
	A:
		lastSessionId = s.ID
	}
	if len(bulkItems) <= 0 {
		return
	}
	bi, err := esutil.NewBulkIndexer(esutil.BulkIndexerConfig{
		Client:     es,
		Index:      variable.Config.Elasticsearch.Index,
		NumWorkers: 4,
	})
	if err != nil {
		return
	}
	for _, item := range bulkItems {
		if err := bi.Add(context.Background(), *item); err != nil {
			fmt.Println(err.Error())
			continue
		}
	}
	if err := bi.Close(context.Background()); err != nil {
		fmt.Println(err.Error())
	}

	if len(labelItems) > 0 {
		labelBi, err := esutil.NewBulkIndexer(esutil.BulkIndexerConfig{
			Client:     es,
			Index:      variable.Config.Elasticsearch.LabelIndex,
			NumWorkers: 4,
		})
		if err != nil {
			return
		}
		for _, item := range labelItems {
			if err := labelBi.Add(context.Background(), *item); err != nil {
				fmt.Println(err.Error())
				continue
			}
		}
		if err := labelBi.Close(context.Background()); err != nil {
			fmt.Println(err.Error())
		}

	}
	if isWrite {
		variable.DB.Redis.Set(syncRedisKey, lastSessionId, 0)
	}
}
